package br.mikhas.reflector.method;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;

import br.mikhas.reflector.ReflectionUtils;
import br.mikhas.reflector.method.invoker.MethodInvoker;
import br.mikhas.reflector.method.invoker.MethodInvokerFactory;

public class ObjectMethodProxy extends ClassMethodProxy {

	protected final Object object;
	private static int CREATE_INVOKER = 50;
	private int invokes = 0;
	private MethodInvoker invoker;

	public ObjectMethodProxy(Class<?> clazz, Method method, Object object) {
		super(clazz, method);
		assert object != null : "Object cannot be null";
		this.object = object;
	}

	public Object invoke(Object... args) {
		if (args.length == 0) {
			return this.invoke();
		}

		if (invoker != null)
			return invoker.invoke(object, args);

		if (invokes >= CREATE_INVOKER) {
			int modifiers = method.getModifiers();

			if (Modifier.isPublic(modifiers) && !Modifier.isStatic(modifiers)) {
				useNativeInvoker();
				return invoker.invoke(object, args);
			}
		}

		try {

			if (method.isAccessible())
				return this.method.invoke(this.object, args);
			else {
				method.setAccessible(true);
				Object result = this.method.invoke(this.object, args);
				method.setAccessible(false);
				return result;
			}
		} catch (IllegalArgumentException e) {
			Class<?>[] types = ReflectionUtils.getArgumentsTypes(args);
			String names = ReflectionUtils.typeNames(types);
			throw new MethodInvocationException("Illegal argument types "
					+ names, e);
		} catch (IllegalAccessException e) {
			throw new MethodInvocationException("Access denied to method:"
					+ signature(), e);
		} catch (InvocationTargetException e) {
			throw new MethodInvocationException("Error while invoking method: "
					+ signature(), e);
		}
	}

	public Object invoke() {

		if (invoker != null)
			return invoker.invoke(object);

		if (invokes >= CREATE_INVOKER) {

			int modifiers = method.getModifiers();

			if (Modifier.isPublic(modifiers) && !Modifier.isStatic(modifiers)) {
				useNativeInvoker();
				return invoker.invoke(object);
			}
		}

		invokes++;

		try {
			if (method.isAccessible())
				return this.method.invoke(this.object);
			else {
				method.setAccessible(true);
				Object result = this.method.invoke(this.object);
				method.setAccessible(false);
				return result;
			}
		} catch (IllegalArgumentException e) {
			throw new MethodInvocationException("Illegal argument", e);
		} catch (IllegalAccessException e) {
			throw new MethodInvocationException("Access denied to method:"
					+ signature(), e);
		} catch (InvocationTargetException e) {
			throw new MethodInvocationException("Error while invoking method: "
					+ signature(), e);
		}
	}

	public Object forceInvoke(Object... args) {
		if (this.method.isAccessible()) {
			return this.invoke(args);
		} else {
			this.method.setAccessible(true);
			Object res = this.invoke(args);
			this.method.setAccessible(false);
			return res;
		}

	}

	@Override
	public void useNativeInvoker() {
		this.invokes = 1000;
		invoker = MethodInvokerFactory.getDefault().getFor(method);
	}
}
